/* 
[crudeSP 0.9.10.5.3] 
Copyright (C) 2015-2016 Gerald Steinwender This program is free software: you can redistribute it and/or modify it under the terms of the GNU 
General Public License as published by the Free Software Foundation, version. This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 
License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see 
<http://www.gnu.org/licenses/>. 
*/
var crudeSP = crudeSP || {};
var csp = crudeSP;      //alernative name

//namespace: user
crudeSP.user = crudeSP.user || {};

//////////////////////////////////// U S E R - M O D U L E ///////////////////////////////
//SP-Username       cannot fetch users of foreign sites 
crudeSP.user.getName = function (callBack, callBackFail) {
    crudeSP.user.context = SP.ClientContext.get_current();
    crudeSP.user.web = crudeSP.user.context.get_web();

    var name = "";
    name = crudeSP.user.web.get_currentUser();
    crudeSP.user.context.load(name);
    crudeSP.user.context.executeQueryAsync(userSuccess, noUsername);

    function noUsername(sender, args) {
        if (typeof callBackFail !== "undefined") {
            callBackFail(args.get_message());
        }
    }

    function userSuccess() {
        callBack(name.get_title());
    }
};
// /SP-Username

//Groupmembership
crudeSP.user.isMemberOf = function (user, group, callBack, callBackFail) {
    var groups = crudeSP.user.web.get_siteGroups();
    var targetGroup = groups.getByName(group);
    var users = targetGroup.get_users();
    crudeSP.user.context.load(targetGroup);
    crudeSP.user.context.load(users);
    crudeSP.user.context.executeQueryAsync(success, fail);

    function success() {
        var userIsInGroup = false;
        var usersEnumerator = users.getEnumerator();
        while (usersEnumerator.moveNext()) {
            var currUser = usersEnumerator.get_current();
            if (currUser.get_title() == user) {
                userIsInGroup = true;
                break;
            }
        }
        callBack(userIsInGroup);
    }

    function fail() {
        if (typeof callBackFail !== "undefined") {
            callBackFail("Failed to get groupmembership of user '" + user + "' . Error:" + args.get_message());
        }
    }
}
// /Groupmembership
//////////////////////////////////// /U S E R - M O D U L E ///////////////////////////////


////////////////////////////////////////   SHARED   ///////////////////////////////////////
crudeSP.shared = {};

//shared with csp.Operation
crudeSP.shared.inlineProps = {};           //to be shared between caml builder and read list, constants to written in the result-object
crudeSP.shared.allowedTabCol = /[\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\w\+#!*\/%\?\-\_]+/;        //=> instead of \w+
crudeSP.shared.allowedTabColwDates = /[\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\w\+#!*\/%\?\-\_\:\.\s+]+/;
crudeSP.shared.inlineStatementRgx = new RegExp("\\(" + crudeSP.shared.allowedTabColwDates.source + "\\s+as\\s+" + crudeSP.shared.allowedTabCol.source + "\\)", "i");
crudeSP.shared.wordDotWord = new RegExp(crudeSP.shared.allowedTabCol.source + "\\." + crudeSP.shared.allowedTabCol.source);          //=> instead of \w+\.\w+
crudeSP.shared.wordSEPWord = new RegExp(crudeSP.shared.allowedTabCol.source + "\\_SEP\\_" + crudeSP.shared.allowedTabCol.source);    //=> Seperator f?r Aliases _SEP_
crudeSP.shared.wordSpaceWord = new RegExp(crudeSP.shared.allowedTabCol.source + "\\s+" + crudeSP.shared.allowedTabCol.source);       //=> instead of \w+\s+\w+
crudeSP.shared.globDisallow = /[\"\[\]\{\}\=]+/;  //\(\)
crudeSP.shared.camlSelectFlds = [];

crudeSP.shared.handleInlineExp = function (index, mode) {
    var fieldArray = [];
    if (index === 0 || mode === "builder") {
        if (crudeSP.shared.camlSelectFlds.length > 0) {
            fieldArray = crudeSP.shared.camlSelectFlds;
        } else {
            fieldArray = this.caml_select.split(",");
        }
    }
    if (fieldArray[index].match(crudeSP.shared.inlineStatementRgx)) {
        var match = crudeSP.shared.inlineStatementRgx.exec(fieldArray[index])[0];
        fieldArray.splice(index, 1);
        var parts = match.replace(/\(|\)/g, "").split(/\s+as\s+/i);
        var isDate = crudeSP.shared.handleDates(parts[0], "read");     //dateobject for returned dataobjects should be in expected timezone (not sharepoints)
        if (!isNaN(parts[0])) {
            parts[0] = parseInt(parts[0]);
        } else if (isDate) {
            parts[0] = isDate;
        }
        crudeSP.shared.inlineProps[parts[1].trim()] = parts[0];
    }
};

crudeSP.shared.handleDates = function (input, mode) {
    if (typeof input === "object") {            //need to convert the date into a string BUT without the notorious timezone-conversion
        var year = input.getFullYear().toString();
        var month = (function () {
            var monthOfDate = input.getMonth() + 1;
            if (monthOfDate < 10) {
                return "0" + monthOfDate;
            } else {
                return monthOfDate;
            }
        })().toString();
        var day = (function () {
            var dayOfDate = input.getDate();
            if (dayOfDate < 10) {
                return "0" + dayOfDate;
            } else return dayOfDate;
        })().toString();
        var hours = input.getHours().toString();
        var mins = input.getMinutes().toString();
        var secs = input.getSeconds().toString();
        var mils = input.getMilliseconds().toString();
        input = year + "-" + month + "-" + day + " " + hours + ":" + mins + ":" + secs + "." + mils;
    }
    if (typeof input !== "string") {
        return false;
    }
    //fix dateinput to ISO-Date (with space or 'T' as date-time-separator)   accept:
    if (input.match(/\d{4}-\d{2}-\d{2}(\s+|T)\d{2}\:\d{2}\:\d{2}\.\d{3}/) || //YYYY-MM-DD HH:MM:SS.sss OR YYYY-MM-DDTHH:MM:SS.sss
        input.match(/\d{4}-\d{2}-\d{2}(\s+|T)\d{2}\:\d{2}\:\d{2}/) ||        //YYYY-MM-DD HH:MM:SS OR YYYY-MM-DDTHH:MM:SS
        input.match(/\d{4}-\d{2}-\d{2}(\s+|T)\d{2}\:\d{2}/) ||               //YYYY-MM-DD HH:MM OR YYYY-MM-DDTHH:MM
        input.match(/\d{4}-\d{2}-\d{2}/)) {                                  //YYYY-MM-DD

        if (mode === "write") { //Sharepoint expects dates to be posted in UTC-format
            try {
                return new Date(new Date(input).toUTCString()).toISOString();  //return the date as ISO-Date, if valid (it has to be converted to UTC first as SP expects it as UTC and would display a wrong date if not)
            } catch (e) {
                if (debugMode) {
                    console.log("[crudeSP] CAML-BUILDER - WARNING: An invalid date was found in 'where'. It was treated as string (n. W04)");
                }
                return false; //invalid dates and other formats get treated as strings (Value Type = 'Text')
            }
        } else if (mode === "read") {                                         //when reading data, conversion to UTC is not allowed (no match as dates are treated as expected here)
            try {
                input = input.replace(/\s+/, "T");                                  //Swap space with ISO-Date's 'T' Separator
                if (input.length > 10) {                                            //If date has timeinfo (longer then 10 digits) add a Z to the end
                    input += "Z";                                                   //of the string to prevent timezone conversion 
                }
                return new Date(new Date(input).toUTCString()).toISOString(); //return the date as ISO-Date, if valid (it has to be converted to UTC first as SP expects it as UTC and would display a wrong date if not)
            } catch (e) {
                if (debugMode) {
                    console.log("[crudeSP] CAML-BUILDER - WARNING: An invalid date was found in 'where'. It was treated as string (n. W04)");
                }
                return false; //invalid dates and other formats get treated as strings (Value Type = 'Text')
            }
        }
    }
    return false;
}


crudeSP.shared.encodeNames = function (toEncode) {
    //adaption of foreign code: http://www.n8d.at/blog/encode-and-decode-field-names-from-display-name-to-internal-name/
    //encode special chars 
    var charToEncode = toEncode.split('');
    var encodedString = "";

    for (var i = 0; i < charToEncode.length; i++) {
        var encodedChar = escape(charToEncode[i]);
        if (encodedChar.length > 1) {
            encodedChar = encodedChar.toLowerCase();    //die Zeichenkette, mit der das Sonderzeichen ?berschreiben wird muss lowerCase sein.
        }

        if (encodedChar.length == 3) {
            encodedString += encodedChar.replace("%", "_x00") + "_";
        }
        else if (encodedChar.length == 5) {
            encodedString += encodedChar.replace("%u", "_x") + "_";
        }
        else {
            encodedString += encodedChar;
        }
    }
    return encodedString;
}
//shared with csp.Operation

////////////////////////////////////////   /SHARED   ///////////////////////////////////////


///////////////////// C A M L   B U I L D E R /////////////////////
crudeSP.camlBuilder = function (definition) {
    var camlSelect = definition.select;
    var query = definition.where;
    var take = definition.take;
    var debugMode = definition.debug;
    var filter = definition.filter;
    var list = definition.list;
    var orderByDef = definition.orderBy;
    var caml_selectFields = [];
    var viewFields = [];        //if there is no select property set, take fields within this array as viewfields (but only for returned queries - readList will not work this way)
    var validCaml = ""; //final caml output
    var operands = {
        straightOperands: ["<>", "=", "!=", "<", "<=", ">", ">=", "IS NULL", "IS NOT NULL", "CONTAINS", "BEGINS WITH", "IN", "and", "or"],              //and + or need a regex to prevent matching within strings
        straightOperandsRgx: [/<>/, /=/, /!=/, /</, /<=/, />/, />=/, /\s+IS\s+NULL[\s]*/, /\s+IS\s+NOT\s+NULL[\s]*/, /\s+CONTAINS\s+/, /\s+BEGINS\s+WITH\s+/, /\s+IN(\s+|')/, /(\s+and\s+)|(^and[\s]*$)/, /(\s+or\s+)|(^or[\s]*$)/],
        camlOperands: ["<Neq>", "<Eq>", "<Neq>", "<Lt>", "<Leq>", "<Gt>", "<Geq>", "<IsNull>", "<IsNotNull>", "<Contains>", "<BeginsWith>", "<In>", "<And>", "<Or>"],
        camlOperandsClosed: ["</Neq>", "</Eq>", "</Neq>", "</Lt>", "</Leq>", "</Gt>", "</Geq>", "</IsNull>", "</IsNotNull>", "</Contains>", "</BeginsWith>", "</In>", "</And>", "</Or>"]
    };
    var orderByFromString = []; //debug
    var aliases = [];           //debug
    var aliasesWhere = [];      //debug
    var aliasesOrderBy = [];    //debug



    function replaceEscapes(input) {
        input = input.replace(/_escapedCOMMA_/, ",");
        input = input.replace(/\_escapedQUOTE\_/, "'").replace(/\\/g, "");
        input = input.replace("_escapedBRACKETopen_", "(");
        input = input.replace("_escapedBRACKETcls_", ")");
        input = input.replace(/\_escaped(and|or)\_/gi, "$1");
        return input;
    }

    function escapeAwareReplacer(input, mode) {
        input = input.replace(/\\'/g, "_escapedQUOTE_");    //escape '
        input = input.replace(/\\\(/g, "_escapedBRACKETopen_"); //escape (
        input = input.replace(/\\\)/g, "_escapedBRACKETcls_");  //escape )
        var noOfQuotes = 0;
        var returnString = "";
        if (!input.match(/(and|or|\,)/i)) {
            return input;
        }

        (function searchString(string) {
            if (string === "") {
                return;
            }
            var indexReplace;
            var indexQuote = string.indexOf("'");
            var word;
            if (indexQuote === -1) {
                indexQuote = 10000000;
            }
            if (mode === "operand") {
                var indexAnd = /(\s+and\s+)|(^and\s+)/.exec(string);
                var indexOr = /(\s+or\s+)|(^or\s+)/.exec(string);

                if (indexAnd) {
                    indexAnd = indexAnd.index;
                } else {
                    indexAnd = 10000000;
                }
                if (indexOr) {
                    indexOr = indexOr.index;
                } else {
                    indexOr = 10000000;
                }

                if (indexAnd > indexOr) {
                    indexReplace = indexOr;
                    word = "or";
                } else {
                    indexReplace = indexAnd;
                    word = "and";
                }
            } else if (mode === "comma") {
                indexReplace = string.indexOf(",");
                word = ",";
            }
            if (indexReplace === -1) {
                indexReplace = 10000000;
            }

            if (indexQuote < indexReplace) {
                if (noOfQuotes === 0) {
                    noOfQuotes++;
                } else {
                    noOfQuotes--;
                }
                returnString += string.substring(0, indexQuote + 1);
                string = string.substr(indexQuote + 1);
                searchString(string);
                return;
            }

            if (indexReplace < indexQuote) {
                if (noOfQuotes === 1) {
                    if (mode === "comma") {
                        returnString += string.substring(0, indexReplace + 1).replace(/\,/, "_escapedCOMMA_");
                        string = string.substr(indexReplace + 1);
                        searchString(string);
                        return;
                    } else if (mode === "operand") {
                        returnString += string.substring(0, indexReplace + word.length + 1).replace(word, "_escaped" + word + "_");
                        string = string.substr(indexReplace + word.length + 1);
                        searchString(string);
                        return;
                    }
                    return;
                } else {
                    returnString += string.substring(0, indexReplace + 1);
                    string = string.substr(indexReplace + 1);
                    searchString(string);
                    return;
                }
            }

            if (indexReplace === indexQuote) {
                returnString += string;
                return;
            }
        })(input);
        return returnString;
    }

    //exception-function-CAML-Builder
    function cbException(type, message) {
        this.type = "[crudeSP] CAML-BUILDER - " + type;
        this.message = message;
    }
    // /exception-function-CAML-Builder

    //----EXECUTION STACK-----\\
    if (debugMode) {
        checkInputErrors();
    }
    //assemble CAML-string
    headerAndFilter();
    processWhere();
    validCaml += orderByFn();
    validCaml += joins();
    validCaml += viewFieldsFn();
    return validCaml;
    //----EXECUTION STACK----\\

    //ERROR HANDLING
    function checkInputErrors() {
        //if (typeof camlSelect === "undefined" && typeof query === "undefined") {
        //    throw new cbException("missing parameters", "no where or select entered (n. 901)");
        //}

        if (typeof query !== "undefined") {
            var queryWithoutEscapedBrackets = query.replace(/\\\(/g, "_escapedBRACKETopen_").replace(/\\\)/g, "_escapedBRACKETcls_");

            if (queryWithoutEscapedBrackets.match(/\(/g) || queryWithoutEscapedBrackets.match(/\)/g)) {                           //*do if there is at least one bracket:
                if (queryWithoutEscapedBrackets.match(/\(/g) && queryWithoutEscapedBrackets.match(/\)/g)) {                       //**if there are opening and closing brackets:
                    if (queryWithoutEscapedBrackets.match(/\(/g).length !== queryWithoutEscapedBrackets.match(/\)/g).length) {    //if their count is not even => Ex
                        throw new cbException("invalid token", "missmatch between opening and closing brackets (n. 911)");
                    }
                } else {                                                                        //***there is only one sort of bracket => Ex
                    throw new cbException("invalid token", "missmatch between opening and closing brackets (n. 911)");
                }
            }
        }
        if (typeof take !== "undefined") {
            if (take % 1 !== 0 || take < 0) {
                throw new cbException("invalid token", "Value for take must be a positive integer (n. 912)");
            }
        }
    }

    function checkFieldIsExistent(input) {
        var listColumns = csp.camlBuilder.arguments[1];
        for (var i in listColumns) {
            listColumns[i] = crudeSP.shared.encodeNames(listColumns[i]);
        }
        if (input.match(crudeSP.shared.wordDotWord) || input.match(crudeSP.shared.wordSEPWord)) {
            console.log("[crudeSP] WARNING: Field '" + input.replace(/_SEP_/, ".") + "' cannot be checked for presence in non-primary list (n. W03)");
        }
        //4.7 FileLeafRef, FileRef, FileDirRef not contained in query for listcolumns.. don't know why... ???!!?
        if (typeof listColumns !== "undefined" && listColumns.indexOf(input) === -1 && input !== "FileLeafRef" && input !== "FileRef" && input !== "FileDirRef") { //listColumns is undefined when solely using csp.camlBuilder
            throw new cbException("invalid reference", "field '" + input + "' does not exist in list '" + list + "'. Fields are case-sensitive. (n. 925)");
        }
    }

    function findIllegalTokens(input) {
        if (input.match(crudeSP.shared.globDisallow)) {
            throw new cbException("invalid token", "illegal character found in alias: " + input + " (n. 915)");
        }
    }
    // /ERROR HANDLING

    function headerAndFilter() {
        validCaml = "<View>";   //opens the caml-string

        //25.2  top xx rows
        if (typeof take !== "undefined") {
            validCaml += "<RowLimit Paged='False'>" + take + "</RowLimit>";
        }
        // /25.2

        if (typeof filter !== "undefined") {
            var method = filter.readOperationName || "ReadList";
            validCaml += "<Method Name='" + method + "'>";           //SP-Designer calls it Read List whereas visual studio calls it ReadList
            validCaml += "<Filter Name='" + filter.name + "' Value='" + filter.value + "' />";
            validCaml += "</Method>";
        }
        validCaml += "<Query>";
    }

    function processWhere() {
        //uniformize keywords (case sensivity)
        if (typeof query !== "undefined") {
            validCaml += "<Where>";
            query = query.replace(/\s+and\s+/gi, " and ");
            query = query.replace(/\s+or\s+/gi, " or ");
            query = query.replace(/is\s+null/gi, "IS NULL");
            query = query.replace(/is\s+not\s+null/gi, "IS NOT NULL");
            query = query.replace(/\s+contains\s+/gi, " CONTAINS ");
            query = query.replace(/begins\s+with/gi, "BEGINS WITH");
            query = query.replace(/\s+in[s+]?/gi, " IN ");

            convertWhereString(query); //vv
        }
        //19.10 - this mechanism has apparently changed, an empty where-tag is now allowed in external lists too and facilitates get-all-items-queries
        //else {
        //    if (!filter) {
        //        //if no bdc-filter is used, an empty where tag facilitates a get-all-items-query,
        //        //when using bdc-filter, an empty where tag is not allowed and breaks the query
        //        validCaml += "<Where></Where>";
        //    }
        //}
    }

    function convertWhereString(qryString) {
        var output = [];
        if (qryString.match(/\'/)) {  //if escape char ' is found
            qryString = escapeAwareReplacer(qryString, "operand");
        }

        (function slicer(queryString) {
            var n_and = (queryString.match(/\s+and\s+|^and\s+/g) || []).length;
            var n_or = (queryString.match(/\s+or\s+/g) || []).length;            //new match: /\s+or\s+|^or\s+/g
            var n_brckOp = (queryString.match(/\(/g) || []).length;
            var n_brckCls = (queryString.match(/\)/g) || []).length;

            //breaking the recursion
            if (n_and == 0 && n_or == 0 && n_brckOp == 0 && n_brckCls == 0) {
                output.push(queryString);
                return;
            }

            //create an array of operands, sorted by their occurrance 
            var position = [];
            (function positioning() {
                for (var iPos = 0; iPos < 6; iPos++) {
                    var operand;
                    if (iPos === 0) {
                        operand = " and ";
                    } else if (iPos === 1) {
                        operand = "and ";       //can be at the start of a sliced string
                    } else if (iPos === 2) {
                        operand = " or ";
                    } else if (iPos === 3) {
                        operand = "or ";        //can be at the start of a sliced string
                    } else if (iPos === 4) {
                        operand = "(";
                    } else if (iPos === 5) {
                        operand = ")";
                    }

                    //when char not found, indexOf = -1 
                    var index = 100000000;
                    if (queryString.indexOf(operand) !== -1) {
                        index = queryString.indexOf(operand);
                    }

                    position[iPos] = {
                        index: index,
                        sliceby: operand,
                        slicebyLen: operand.length
                    }
                }

                position.sort(function (a, b) {
                    return a.index - b.index;
                });
            })();

            var condition = queryString.substr(0, queryString.indexOf(position[0].sliceby)).trim();

            if (condition !== "") {
                output.push(condition);
            }
            output.push(position[0].sliceby.trim());
            slicer(queryString.substr(queryString.indexOf(position[0].sliceby) + position[0].slicebyLen).trim()); //RECURSION till there are no "and", "or", (, ) in the query
        })(qryString);
        processWhereArray(output); //vv
    }


    function processWhereArray(qryArray) {
        var camlWh = [];

        //ERROR HANDLING
        function checkForMissingOperands(string) {
            var operandFound = false;
            for (var iOperand = 0, iOperandSum = operands.straightOperands.length; iOperand < iOperandSum; iOperand++) {
                if (string.indexOf(operands.straightOperands[iOperand]) !== -1) {
                    operandFound = true;
                }
            }
            if (!operandFound) {
                throw new cbException("invalid token", "no comparing operand in: " + string + " (n. 914)");
            }
        }
        // /ERROR HANDLING

        function translateOperand(inpu) {
            var toReturn = {};
            for (var iSub = 0, iSumSub = operands.straightOperands.length; iSub < iSumSub; iSub++) {

                if (inpu.match(operands.straightOperandsRgx[iSub])) {
                    toReturn.camlOps = operands.camlOperands[iSub];
                    toReturn.normalOps = operands.straightOperands[iSub];
                    toReturn.camlOpsClosed = operands.camlOperandsClosed[iSub];
                }
            }
            return toReturn; //returns an array 0 caml operand; 1 original operand 
        };

        function checkType(ipt) {
            var toReturn = {};
            toReturn.value = ipt;
            var isDate = crudeSP.shared.handleDates(ipt, "read");
            if (!isNaN(ipt)) {
                if (ipt % 1 == 0) {
                    toReturn.typ = "'Integer'";
                } else toReturn.typ = "'Number'";
            } else if (isDate) {
                toReturn.typ = "'DateTime'";
                //toReturn.value = new Date(ipt.replace(/\s/, "T")).toUTCString().toISOString();
                toReturn.value = isDate;
                var timeInfo = toReturn.value.split("T")[1];
                if (timeInfo.match(/[^0\:\.Z]/g)) {
                    toReturn.typ += " IncludeTimeValue='TRUE'";
                }
            } else {
                toReturn.typ = "'Text'";
                toReturn.value = toReturn.value.replace(/'/g, "");
                toReturn.value = replaceEscapes(toReturn.value);
            }

            return toReturn; //returns an objcet (typ, value); if value is DateTime return the ISO-string
        }

        for (var i = 0, iSum = qryArray.length; i < iSum; i++) { //improving performence by setting array length within for-loop-init
            if (qryArray[i] !== "and" && qryArray[i] !== "or" && qryArray[i] !== "(" && qryArray[i] !== ")" && qryArray[i].trim() !== "") {
                var slice = qryArray[i];
                var operand = translateOperand(slice);
                var sliceLeftArr = slice.split(operand.normalOps)[0].trim().split("."); //value before operand

                //ERROR HANDLING
                if (debugMode) {
                    if (slice.match(/\'/)) {
                        if (slice.match(/\'/g).length % 2 !== 0) {
                            slice = replaceEscapes(slice);
                            throw new cbException("invalid token", "missmatch of quotes found at " + slice + " (n. 9112)");
                        }
                    }
                    checkForMissingOperands(slice);
                    var field = slice.split(operand.normalOps)[0].trim();

                    if (typeof csp.camlBuilder.arguments[1] !== "undefined") {   //commons.checkFieldIsExistent is not init when solely using camlBuilder
                        findIllegalTokens(field.replace(/\'/g, ""));
                        checkFieldIsExistent(field.replace(/\'/g, "")); //to allow columnnames to be escaped, delete '' to check for their presence in debug
                    }
                }
                // /ERROR HANDLING

                var sliceLeft = "";
                if (sliceLeftArr.length === 2) {
                    aliasesWhere.push(sliceLeftArr[0]); //debug
                    sliceLeft = sliceLeftArr[0] + "_SEP_" + sliceLeftArr[1]; //replace alias with _SEP_ (separator)]
                } else {
                    sliceLeft = sliceLeftArr.join("");
                }
                //when columns with names like 'and' +'or' are escaped by '..' it works if the quotes removed in the next line
                sliceLeft = sliceLeft.replace(/\'/g, "");

                var sliceRight = slice.split(operand.normalOps)[1].trim().replace(/\_escaped(and|or)\_/gi, "$1"); //value after operand REPLACE ESCAPED AND+OR WITH ORIGINAL (slicing by operand is done now)

                if (viewFields.indexOf(sliceLeft) === -1) {
                    viewFields.push(sliceLeft); //if no select columns are set, use this array for viewfields
                }

                //I. use this pattern when using IN
                if (operand.normalOps === "IN") {
                    //ERROR HANDLING
                    if (debugMode) {
                        (function findInEror() {
                            if (sliceRight.match(/\d+\s+\d+/)) {    // number(s) space number(s)
                                throw new cbException("invalid token", "IN: numbers not separated by commas (n. 912)");
                            }
                        })();
                    }
                    // /ERROR HANDLING
                    camlWh.push(operand.camlOps);
                    camlWh.push("<FieldRef Name = '" + sliceLeft + "' />");      //=> SPENCODE
                    camlWh.push("<Values>");

                    var inValues;
                    if (sliceRight.match(/\'/)) {
                        var sliceRightEscaped = escapeAwareReplacer(sliceRight, "comma");
                        inValues = sliceRightEscaped.split(",");
                    } else {
                        inValues = sliceRight.split(",");
                    }

                    for (var iI = 0, iISum = inValues.length; iI < iISum; iI++) {
                        var typIn = checkType(inValues[iI].trim()); //** 23.6 replace is new (instead of ln 830)
                        var counter = "<Value Type=" + typIn.typ + ">";
                        camlWh.push(counter + typIn.value + "</Value>");
                    }
                    camlWh.push("</Values>");
                    camlWh.push(operand.camlOpsClosed);
                } else { //II. normal pattern without IN
                    camlWh.push(operand.camlOps);
                    camlWh.push("<FieldRef Name = '" + crudeSP.shared.encodeNames(sliceLeft) + "' />");      //=> SPENCODE
                    if (sliceRight !== "") {    //if sliceRight is an empty string, query contains 'is (not) null' => Value Type would break the query
                        var typ = checkType(sliceRight); //** 23.6 replace is new (instead of ln 830)    
                        camlWh.push("<Value Type=" + typ.typ + ">" + typ.value + "</Value>");
                    }
                    camlWh.push(operand.camlOpsClosed);
                }
            } else if (qryArray[i] === "and" || qryArray[i] === "or") {
                camlWh.push(translateOperand(qryArray[i]).camlOps);
            } else if (qryArray[i] === "(" || qryArray[i] === ")") {
                camlWh.push(qryArray[i]);
            }
        }
        sortArrayToCaml(camlWh); //vv
    }

    function sortArrayToCaml(processedArray) {
        if (processedArray.length > 0) {
            var expTree = {};
            var camlString = "";

            (function assembleWhereCaml() {
                var continuePosition = 0;
                (function makeExpressionTree(startPos, currentObj, expressionCount) {
                    var operandCount = expressionCount;
                    for (var i = startPos, iSum = processedArray.length; i < iSum; i++) {
                        //if a logical operator is found
                        if (processedArray[i] === "<And>" || processedArray[i] === "<Or>") {
                            addExpression("behind");
                            addOperand(processedArray[i]);
                            expressionCount++;
                            continuePosition = i + 1;
                            break;
                        }

                        //if an opening bracket ( is encountered 
                        if (processedArray[i] === "(") {
                            //if a closing bracket comes next was here... (TextPad)
                            continuePosition = i + 1;
                            var subObject = {};
                            makeExpressionTree(continuePosition, subObject, 0); //restart for Subobject with processedArray[i + 1]
                            currentObj["exp" + expressionCount] = subObject;
                            break;
                        }

                        //if a closing bracket ) is encountered
                        if (processedArray[i] === ")") {
                            addExpression("behind");
                            continuePosition = i + 1;
                            return;
                        }

                        //if end of query string is reached
                        if (i >= processedArray.length - 1) {
                            for (var iLookFwd = continuePosition; iLookFwd < processedArray.length; iLookFwd++) {
                                if (operands.camlOperands.indexOf(processedArray[iLookFwd]) !== -1) {
                                    addExpression("forward");
                                    break;
                                }
                            }
                            continuePosition = 0;
                        }
                    }

                    if (continuePosition > 0 && continuePosition < processedArray.length) {
                        makeExpressionTree(continuePosition, currentObj, expressionCount);
                    }

                    function addExpression(mode) {
                        var maxPosition;

                        if (mode === "behind") {
                            maxPosition = i;
                        } else if (mode === "forward") {
                            maxPosition = processedArray.length;
                        }

                        if (continuePosition !== maxPosition) {
                            currentObj["exp" + expressionCount] = setExpression();
                        }

                        function setExpression() {
                            var expression = [];
                            for (var iExp = continuePosition; iExp < maxPosition; iExp++) {
                                if (processedArray[iExp] !== ")")
                                    expression.push(processedArray[iExp]);
                            }
                            return expression;
                        }
                    }

                    function addOperand(op) {
                        currentObj["op" + operandCount] = op;
                    }

                })(continuePosition, expTree, 0);

                //expressionTree => validCaml goes here...
                (function renderCamlFromExpTree(object) {
                    var activeOperand; //iterateProperty sets activeOperand on properties starting with 'op'
                    var closingOperandsChain = []; //operand chain for multiple operators in one level

                    //if object is not an array
                    if (object.constructor !== Array) {
                        if (!object.hasOwnProperty("op0")) { //if object has only one expression (no operator) a unnecessary bracket was used in the query string, restart renderCamlFromExpTree with this object
                            renderCamlFromExpTree(object["exp0"]);
                            return;
                        }

                        for (var iterator = 0, howOften = Object.keys(object).length; iterator < howOften; iterator++) {
                            iterateProperty("op", iterator); //do it for operands first, they have to come first
                            iterateProperty("exp", iterator); //do it for expressions
                        }
                    } else { //if object is array a single expression without operator is enclosed with a bracket, which is unnecessary and would break the query, just add the expression to the camlstring --       
                        camlString += object.join("");
                    }

                    if (closingOperandsChain.length > 0) {
                        for (var iOPChain = closingOperandsChain.length - 1; iOPChain >= 0; iOPChain--) {
                            camlString += closingOperandsChain[iOPChain];
                        }
                    }
                    if (typeof activeOperand !== "undefined") {
                        camlString += operands.camlOperandsClosed[operands.camlOperands.indexOf(activeOperand)]; //close the activeOperand with appropriate closing caml operand ***
                    }


                    function iterateProperty(propType, propertyIterator) {
                        for (var propertyOp in object) {

                            if (propertyOp === propType + propertyIterator) {
                                if (propType !== "exp") { //operators
                                    activeOperand = object[propType + propertyIterator];
                                    camlString += object[propType + propertyIterator];
                                    if (propertyIterator !== 0) { //one push into operand chain when index > 0, the fist operand gets closed on leaving the function ***
                                        closingOperandsChain.push(operands.camlOperandsClosed[operands.camlOperands.indexOf(activeOperand)]);
                                    }
                                } else { //expressions
                                    if (object[propType + propertyIterator].constructor === Array) {    //2.7
                                        camlString += object[propType + propertyIterator].join("");
                                    } else {
                                        renderCamlFromExpTree(object[propType + propertyIterator]);
                                        return;
                                    }
                                }
                            }
                        }
                    }
                })(expTree);
                validCaml += camlString;
                validCaml += "</Where>";
            })();
        }
    }

    //ORDER BY
    function orderByFn() {
        var orderBy = "";
        if (typeof orderByDef !== "undefined" && orderByDef !== null) {
            orderBy = "<OrderBy>";
            var splittedCaml_orderBy = orderByDef.split(",");
            for (var iOb = 0, iObSum = splittedCaml_orderBy.length; iOb < iObSum; iOb++) {
                var itemAndDirection = splittedCaml_orderBy[iOb].trim();
                //ERROR HANDLING
                if (debugMode) {
                    (function () {
                        var checkObFld = splittedCaml_orderBy[iOb].replace(/\s+asc|\s+desc/gi, ""); //f?r das orderBy crudeSP.camlBuilder.caml_selectFields[iCS].replace(/\s(desc|asc)/gi, "");
                        if (checkObFld.match(crudeSP.shared.wordSpaceWord)) {
                            throw new cbException("invalid token", "orderBy fields need to be divided by comma (n. 9110)");
                        }
                    })();
                }
                // /ERROR HANDLING
                var item = itemAndDirection.split(/\s+/)[0].replace(/\./g, "_SEP_");
                if (item.match(/_SEP_/)) {
                    aliasesOrderBy.push(item.split("_SEP_")[0]); //debug  
                }
                var direction2Caml = "TRUE";
                if (itemAndDirection.match(/\s+desc/i)) {
                    direction2Caml = "FALSE";
                }
                orderByFromString.push(item); //-->needed later for ERROR HANDLING
                orderBy += "<FieldRef Name='" + crudeSP.shared.encodeNames(item) + "' Ascending='" + direction2Caml + "' />";        //=> SPENCODE
            }
            orderBy += "</OrderBy>";
        }
        orderBy += "</Query>";
        return orderBy;
    }
    // /ORDER BY

    //CAML JOIN             creates XML-Properties <Joins> and <ProjectedFields>
    function joins() {
        var joinCaml = "";
        var projectedFlds = "";
        if (typeof list !== "undefined" && (list !== null)) {

            if (list.match(/\s+(left[-\s_]?)?join\s+/gi)) {
                joinCaml = (function () {
                    var joinsCaml = "<Joins>";
                    list = list.replace(/\s+join\s+/gi, " join ");
                    list = list.replace(/\s+left[-\s+_]?join\s+/gi, " left-join ");
                    list = list.replace(/\s+with\s+/gi, " with ");

                    var splittedFrom = list.split(",");
                    var parentList = "";
                    for (var iFrom = 0, iFromSum = splittedFrom.length; iFrom < iFromSum; iFrom++) {
                        var splitBy = /join/;
                        var type = "INNER";
                        if (splittedFrom[iFrom].match(/left[-\s_]?join/i)) {
                            splitBy = /left[-\s_]?join/i;
                            type = "LEFT";
                        }
                        var listSrc = splittedFrom[iFrom].split(splitBy)[0].trim();
                        var listJoin = splittedFrom[iFrom].split(splitBy)[1].trim();
                        var aliasNforeignKey = listJoin.split("with");
                        var currentAlias = aliasNforeignKey[0].trim();

                        //ERROR HANDLING 
                        if (debugMode) {
                            (function checkJoin() {
                                //right-join is not supported in CAML
                                if (list.match(/right[-\s+_]?join/gi)) {
                                    throw new cbException("not supported", "right join is not supported by the system (n. 931)");
                                }
                                if (splittedFrom[iFrom].match(/(\s+(left[-\s_]?)?join[\w\s]+?){2}/i)) {
                                    throw new cbException("invalid token", "join-operations need to be devided with a comma (n. 917)");
                                }
                            })();
                            findIllegalTokens(currentAlias);
                        }
                        // /ERROR HANDLING

                        aliases.push(currentAlias);
                        var foreignKey = aliasNforeignKey[1].trim().replace(/.*\./, "");

                        if (iFrom === 0) {
                            parentList = listSrc;
                        }
                        var printListSrc = "";

                        if (listSrc !== parentList && listSrc !== "") {
                            printListSrc = "List='" + listSrc + "'";
                        }
                        joinsCaml += "<Join Type='" + type + "' ListAlias='" + currentAlias + "' >";
                        joinsCaml += "<Eq>";
                        joinsCaml += "<FieldRef " + printListSrc + " Name='" + crudeSP.shared.encodeNames(foreignKey) + "' RefType='Id' />";     //=>SPENCODE
                        joinsCaml += "<FieldRef List='" + currentAlias + "' Name='ID' />";
                        joinsCaml += "</Eq>";
                        joinsCaml += "</Join>";
                    }

                    joinsCaml += "</Joins>";
                    return joinsCaml;

                })();


                projectedFlds = (function () {
                    if (typeof camlSelect !== "undefined") {
                        caml_selectFields = camlSelect.split(",");
                        crudeSP.shared.camlSelectFlds = caml_selectFields;
                    } else {
                        caml_selectFields = viewFields;
                        crudeSP.shared.camlSelectFlds = viewFields;
                    }
                    var projFieldsCaml = "<ProjectedFields>";
                    for (var aI = 0, aISum = aliases.length; aI < aISum; aI++) {
                        for (var iSF = 0, iSFSum = caml_selectFields.length; iSF < iSFSum; iSF++) {
                            if (caml_selectFields[iSF].indexOf(".") !== -1 || caml_selectFields[iSF].indexOf("_SEP_") !== -1) {
                                var splitBy = ".";
                                if (caml_selectFields[iSF].indexOf("_SEP_") !== -1) {
                                    splitBy = "_SEP_";
                                }
                                var ali = caml_selectFields[iSF].split(splitBy)[0].trim();
                                var column = caml_selectFields[iSF].split(splitBy)[1].trim().replace(/\s+as\s+/gi, " as ");

                                if (column.match(/\s+as\s+[^.]*/i)) {
                                    column = column.split(/\s+as\s+/i)[0]; //split(" as ")
                                }
                                if (ali === aliases[aI]) {
                                    projFieldsCaml += "<Field ShowField='" + crudeSP.shared.encodeNames(column) + "' Type='Lookup' Name='" + ali + "_SEP_" + crudeSP.shared.encodeNames(column) + "' List='" + ali + "' />"; //=>SPENCODE      alias-Spalte mit _ trennen, das liest auch caml
                                    var theAs = "";
                                    if (caml_selectFields[iSF].match(/\s+as\s+[^.]*/i)) {
                                        theAs = " as " + caml_selectFields[iSF].split(/\s+as\s+/i)[1];       //split(" as ")
                                    }
                                    caml_selectFields.splice(iSF, 1, ali + "_SEP_" + column + theAs);
                                }
                            }
                        }
                    }
                    projFieldsCaml += "</ProjectedFields>";
                    return projFieldsCaml;
                })();
            }
        }
        return joinCaml + projectedFlds;
    }

    // /CAML JOIN

    //VIEWFIELDS
    function viewFieldsFn() {
        var viewFieldsCaml = "<ViewFields>";
        if (typeof camlSelect === "undefined") {
            //DEFAULT-BEHAVIOR: no viewfields set, take the fields used in where
            generateVFCaml(viewFields);
        } else {
            var viewFieldsFromString = [];
            if (caml_selectFields.length === 0) {
                caml_selectFields = camlSelect.split(",");
                crudeSP.shared.camlSelectFlds = camlSelect.split(",");
            }
            for (var iCS = 0, iCSSum = caml_selectFields.length; iCS < iCSSum; iCS++) {
                var baseField = caml_selectFields[iCS].trim();
                //30.3 -inline Statements
                if (caml_selectFields[iCS].match(crudeSP.shared.inlineStatementRgx)) {
                    crudeSP.shared.handleInlineExp(iCS, "builder");
                    // /30.3 - inline Statements
                } else {
                    baseField = baseField.replace(/\s+as\s+/gi, " as ");
                    //ERROR HANDLING
                    if (debugMode) {
                        (function () {
                            var checkSelFld = caml_selectFields[iCS].replace(new RegExp("\\s+as\\s+" + crudeSP.shared.allowedTabCol.source, "gi"), "").trim();
                            // 6.7
                            checkSelFld = checkSelFld.replace(/\'/g, "");
                            // /6.7
                            //30.3 => applies to inline expressions
                            if (caml_selectFields[iCS].match(/\(/g) || caml_selectFields[iCS].match(/\)/g)) {
                                if (caml_selectFields[iCS].match(/\(/g) && caml_selectFields[iCS].match(/\)/g)) {
                                    if (caml_selectFields[iCS].match(/\(/g).length !== caml_selectFields[iCS].match(/\)/g).length) {
                                        throw new cbException("invalid token", "missmatch between opening and closing brackets found in select at '" + caml_selectFields[iCS].trim() + "' (n. 9113)");
                                    }
                                } else {
                                    throw new cbException("invalid token", "missmatch between opening and closing brackets found in select at '" + caml_selectFields[iCS].trim() + "' (n. 9113)");
                                }

                                if (!caml_selectFields[iCS].match(new RegExp(crudeSP.shared.allowedTabCol.source + "\\s+as\\s+", "i"))) {
                                    throw new cbException("invalid token", "no 'as' found in inline expression '" + caml_selectFields[iCS].trim() + "' (n. 9117)");
                                }
                            }

                            if (typeof csp.camlBuilder.arguments[1] !== "undefined") {
                                if (!checkSelFld.match(crudeSP.shared.wordDotWord) && !checkSelFld.match(crudeSP.shared.wordSEPWord)) {   //1.7 -> we can only check fields for presence in primary tables 
                                    findIllegalTokens(checkSelFld);
                                    checkFieldIsExistent(checkSelFld);
                                }
                            }

                            if ((typeof list !== "undefined" && list !== null) && list.match(/\s(left[-\s+_]?)?join\s+/gi) && camlSelect.match(/_SEP_/g)) {
                                throw new cbException("invalid token", "field: '" + caml_selectFields[iCS] + "' cannot be contain '_SEP_' when using join (n. 919)");
                            }
                        })();
                    }
                    // /ERROR HANDLING

                    //select as
                    if (baseField.match(/\sas\s[^.]*/)) {
                        var splittedBaseField = baseField.split(/\s+as\s+/i); //split(" as ")
                        viewFieldsFromString.push(splittedBaseField[0].trim());
                    } else {
                        viewFieldsFromString.push(baseField);
                    }
                    // /select as
                }
            }
            generateVFCaml(viewFieldsFromString);
        }

        function generateVFCaml(fields) {
            var aliasesSelect = [];     //debug
            for (var iVF = 0, iVFSum = fields.length; iVF < iVFSum; iVF++) {
                fields[iVF] = fields[iVF].replace(/\'/g, "");   //as can also be escaped, and its doing it right, but sharpoint says field is not existent
                if (fields[iVF].match(crudeSP.shared.wordDotWord) || fields[iVF].match(crudeSP.shared.wordSEPWord)) {
                    aliasesSelect.push(fields[iVF].split(/\.|\_SEP\_/)[0]);
                }

                //ERROR HANDLING
                if (debugMode) {
                    (function findAliasErrors() {
                        for (var iAl = 0, iAlSum = aliases.length; iAl < iAlSum; iAl++) {
                            if (aliases[iAl].match(crudeSP.shared.globDisallow)) {
                                throw new cbException("invalid token", "illegal character found in alias: " + aliases[iAl] + "  (n. 9111)");
                            }
                        }
                        for (var iAOb = 0, iAObSum = aliasesOrderBy.length; iAOb < iAObSum; iAOb++) {
                            thereAintNoAliasInList(aliasesOrderBy[iAOb]);
                            if (aliases.indexOf(aliasesOrderBy[iAOb]) === -1) {
                                throw new cbException("invalid reference", "alias: " + aliasesOrderBy[iAOb] + " in orderBy not found in list (n. 921)");
                            }
                        }
                        for (var iAW = 0, iAWSum = aliasesWhere.length; iAW < iAWSum; iAW++) {
                            thereAintNoAliasInList(aliasesWhere[iAW]);
                            if (aliases.indexOf(aliasesWhere[iAW]) === -1) {
                                throw new cbException("invalid reference", "alias: " + aliasesWhere[iAW] + " in where not found in list (n. 922)");
                            }
                        }
                        for (var iSel = 0, iSelSum = aliasesSelect.length; iSel < iSelSum; iSel++) {
                            thereAintNoAliasInList(aliasesSelect[iSel]);
                            if (aliases.indexOf(aliasesSelect[iSel]) === -1) {
                                throw new cbException("invalid reference", "alias: " + aliasesSelect[iSel] + " in select not found in list (n. 924)");
                            }
                        }

                        function thereAintNoAliasInList(fld) {
                            if (aliases.length < 1) {
                                throw new cbException("invalid token", "alias '" + fld + "' not valid. No join-expression found in list (n. 926)");
                            }
                        }
                    })();
                    findIllegalTokens(fields[iVF]);
                }
                // /ERROR HANDLING

                viewFieldsCaml += "<FieldRef Name = '" + crudeSP.shared.encodeNames(fields[iVF]) + "' />";
            }
        }

        //ERROR HANDLING
        if (debugMode) {
            (function checkOrderByFlds() {
                for (var iO = 0, iOSum = orderByFromString.length; iO < iOSum; iO++) {

                    if (crudeSP.camlBuilder.viewFlds.indexOf(orderByFromString[iO]) === -1) { //FALSCHER BEZUG
                        throw new cbException("invalid reference", "orderBy: field '" + orderByFromString[iO].replace(/_SEP_/, ".") + "' not found in selected fields (n. 923)");
                    }
                }
            })();
        }
        // /ERROR HANDLING

        viewFieldsCaml += "</ViewFields>";
        viewFieldsCaml += "</View>"; //closes the caml-string
        return viewFieldsCaml;
    }

    // /VIEWFIELDS


};
///////////////////// /C A M L   B U I L D E R /////////////////////

/////////////////////////////// O P E R A T I O N //////////////////////////////
crudeSP.Operation = function (definition) {

    //input-properties
    this.list = definition.list;
    this.site = definition.site;                //string or undefined = current
    this.query = definition.where;              //string incl. OrderBy or int (getItemById)
    this.filter = definition.filter;            //object=> {name: string; value: string}  
    this.caml_select = definition.select;       //string -> separation through comma  
    this.orderBy = definition.orderBy;          //string separation through comma              
    this.debugMode = definition.debug || false; //debug consumes runtime, disabled by default
    this.take = definition.take;
    this.set = definition.set;
    this.values = definition.values;
    this.customItemPermissions = true;
    this.roles = definition.roles;
    this.file = definition.file;
    this.filename = definition.filename;
    this.roleTypesInterface = ["read", "write", "contribute", "administrator", "guest", "webdesigner"]; //SP also offers: administrator, guest, none, webDesigner
    this.folders = definition.folders; //27.11.15
    this.foldersNew = definition.foldersNew; //1.7.16
    this.folderUrl = definition.folderUrl; //5.7.16
    this.deleteOptions = definition.deleteOptions;
    this.source = definition.source; //13.10.16
    this.destination = definition.destination; //13.10.16

    //privates
    var that = this;
    var caml;                       //global container for computed caml query
    var primaryList;                //primary List
    var site;                       //global site    \
    var web;                        //global web      ==> to be shared between operations
    var context;                    //global context /
    var affectedRows = 0;           //in updateItems and deleteItems

    var selectAs = [];              //also needs to be shared between caml-builder and readItems, contains aliases for columnnames
    var viewFlds = [];              //this array needs to be globalized because it has to be shared between caml-builder and readItems
    var listColumns = [];           //queried columnnames of the primary list (debugging: check if field is present)
    var commons = commons || {};
    var utils = utils || {};

    //primary list
    var splitOp = /\s+(left[-\s_]?)?join\s+/gi;
    //13.10.16 when moving files primary list is not important, so take the the first index in this source as list
    if (typeof that.list === "undefined") {
        if (that.debugMode) {
            if (typeof that.source === "undefined") {
                throw new spqException("MAIN - missing parameters", "property 'list' or source not defined (n. 001)");
            }
        }
        if (that.source.constructor === Array) {
            that.list = that.source[0].substr(0, that.source[0].indexOf("/"));
        } else {
            that.list = that.source.substr(0, that.source.indexOf("/"));
        }
    }
    primaryList = that.list.split(splitOp)[0];
   
    //ERROR HANDLING
    //main exception function
    function spqException(type, message) {
        this.type = "[crudeSP] " + type;
        this.message = message;
    }

    if (that.debugMode) {
        if (that.debugMode) {
            if (typeof SP === "undefined") {
                throw new spqException("missing prerequisites", "Sharepoint client-side libraries not loaded properly (n. 091)");
            }
        }
        
        if (primaryList.match(crudeSP.shared.wordDotWord)) {
            throw new spqException("MAIN - invalild token", "first list in property 'list' cannot have a table-alias (n. 012)");
        }
    }

    //needs to be split between utils.assignRoles and uploadToLibrary as upload must be prevented if errors get thrown here
    function findRolesErrors() {
        var roles = [];

        if (that.roles.constructor !== Array) {
            roles.push(that.roles);
        } else {
            roles = that.roles;
        }

        for (var iR = 0, iRSum = roles.length; iR < iRSum; iR++) {
            if (typeof roles[iR].user !== "undefined" && typeof roles[iR].group !== "undefined") {
                throw new spqException("AssignRoles: invalid token", "cannot have 'user' and 'group' in the same instance (n. 711)");

            }
            if (typeof roles[iR].user !== "undefined") {
                if (roles[iR].user.match(crudeSP.shared.wordSpaceWord)) {
                    throw new spqException("AssignRoles: invalid token", "Users not separated with comma at " + roles[iR].user + " (n. 712)");
                }
            } else if (typeof roles[iR].group !== "undefined") {
                if (roles[iR].group.match(crudeSP.shared.wordSpaceWord)) {
                    throw new spqException("AssignRoles: invalid token", "Groups not separated with comma at " + roles[iR].group + " (n. 713)");
                }
            }
        }
    }
    // /ERROR HANDLING



    if (typeof that.filter !== "undefined" && typeof that.query === "number") {
        that.query = "id = " + that.query;
        //when using bdc-filters, a caml-query has to be passed on to the server, thus getItemById cannot be used
    }
    if (that.list.match("join") && typeof that.query === "number") {
        that.query = "ID = " + that.query;
        //..same as bdc-filters...
    }

    //////////////////////////////////////////// ##### COMMONS ##### /////////////////////////////////////////////////
    //read + update + delete
    commons.GetListAndItems = function () {
        var listItems;
        var list = web.get_lists().getByTitle(primaryList);

        //if (typeof that.query !== "undefined") {    //check typeof that.query === "undefined" removed on 7.3.16 so that an empty where section can also be supported
        if (typeof that.query === "number") {
            listItems = list.getItemById(that.query);
        } else {
            caml = crudeSP.camlBuilder({
                select: that.caml_select,
                list: that.list,
                where: that.query,
                filter: that.filter,
                take: that.take,
                debug: that.debugMode,
                orderByDef: that.orderBy
            }, listColumns);
            var qry = new SP.CamlQuery();
            qry.set_viewXml(caml);
            if (that.folderUrl) {
                qry.set_folderServerRelativeUrl(that.folderUrl);
            }
            listItems = list.getItems(qry);
        }

        if (typeof that.caml_select !== "undefined") {
            (function fillSharedArrays() {
                var select_split = that.caml_select.split(",");
                for (var iSel = 0, iSelSum = select_split.length; iSel < iSelSum; iSel++) {

                    //select as
                    var baseField = select_split[iSel].trim();
                    if (select_split[iSel].match(crudeSP.shared.inlineStatementRgx)) { //inlinestatements aren't removed from viewFlds array in handleInlineExp 
                        continue;
                    }

                    if (baseField.match(/\s+as\s+[^.]*/)) {
                        var splittedBaseField = baseField.split(/\s+as\s+/i); //split(" as ")
                        selectAs.push(splittedBaseField[1].trim()); //=> RAUS
                        viewFlds.push(splittedBaseField[0].trim());
                    } else {
                        selectAs.push(baseField);
                        viewFlds.push(baseField);
                    }
                    // /select as
                }
            })();
        }
        return {
            list: list,
            listItems: listItems
        }
    };

    //read + update + delete
    commons.iteratorFn = function (listItems, execFn) {
        if (typeof listItems.get_count === "undefined") {
            if (typeof listItems.get_fieldValues === "function") {
                affectedRows = 1;
            }
        } else {
            affectedRows = listItems.get_count();
        }

        if (affectedRows === 0) {
            if (that.debugMode) {
                console.log("[crudeSP] MAIN - WARNING: Query returned no results, no rows affected (n. W01)");
            }
            execFn(false);
        }

        if (affectedRows > 0) {
            if (typeof listItems.getEnumerator === "function") {
                //if listItems count is 1, getEnumerator is undefined
                var enumerateList = listItems.getEnumerator();

                while (enumerateList.moveNext()) {
                    var listItemCurrent = enumerateList.get_current();
                    execFn(listItemCurrent);
                }

            } else {
                execFn(listItems);
            }
        }
    };

    //ERROR HANDLING
    //debug: find field error is in update and create
    commons.findFieldErrors = function (toCheck, failClass, string) {
        var method = "";;
        if (failClass === 2) {
            method = "UpdateItems";

        } else if (failClass === 4) {
            method = "CreateItems";
        }
        if (typeof string === "undefined") {
            string = true;
        }

        if (string) {
            if (toCheck.indexOf("=") === -1) {
                throw new spqException(method + ": invalid token", "no equals operator found in '" + toCheck + "' (n. " + failClass + "11)");
            }
            if (toCheck.match(/=/g).length > 1) {
                throw new spqException(method + ": invalid token", "more then one equals operator found in '" + toCheck + "'. Use ',' to separate columns  (n. " + failClass + "12)");
            }
            toCheck = toCheck.split("=")[0].trim();
            if (toCheck.match(crudeSP.shared.wordDotWord)) {
                throw new spqException(method + ": invalid token", "Table alias found in '" + toCheck + "'. Cannot change columns of non-primary tables: (n. " + failClass + "14)");
            }
            if (toCheck.match(crudeSP.shared.globDisallow)) {
                throw new spqException(method + ": invalid token", "illegal character found in '" + toCheck + "' (n. " + failClass + "13)");
            }
        }
        if (typeof listColumns !== "undefined" && listColumns.indexOf(toCheck) === -1 && toCheck !== "FileLeafRef" && toCheck !== "FileRef" && toCheck !== "FileDirRef") {    //listColumns is undefined when solely using csp.camlBuilder - attention FileLefRef and other File-Columns are not contained in requested fields
            throw new spqException(method + ": invalid reference: field '" + toCheck + "' not found in List '" + primaryList + "' - fields are case sensitive (n. " + failClass + "21)");
        }
    };

    commons.checkIfUsersGroupsExist = function (source) {
        var deferred = new $.Deferred();
        var users = [];
        var groups = [];
        var rolesRgx = new RegExp(crudeSP.shared.allowedTabCol.source + "[(\\\\)|(\\/)]+");
        var opName = "";
        if (source === 4) {
            opName = "Insert";
        } else if (source === 2) {
            opName = "Update";
        } else if (source === 5) {
            opName = "Upload";
        } else if (source === 6) {
            opName = "Create Folder";
        }
        if (that.roles.constructor !== Array) {
            var role = that.roles;
            that.roles = [];
            that.roles.push(role);
        }
        (function checkIfRolesExist() {
            for (var iRoles = 0, iRolesSum = that.roles.length; iRoles < iRolesSum; iRoles++) {
                if (that.roleTypesInterface.indexOf(that.roles[iRoles].type) === -1 || typeof that.roles[iRoles].type === "undefined") {
                    //async error-handling
                    var msg = "";
                    if (typeof that.roles[iRoles].type !== "undefined") {
                        msg = "role '" + that.roles[iRoles].type + "' not valid.";
                    } else {
                        msg = "Roletype not defined in object with index " + iRoles + ".";
                    }

                    deferred.reject({
                        type: "[crudeSP] AssignRoles: invalid token",
                        message: msg + " Insert aborted (use read, write, contribute, administrator, guest, webdesigner) (n. 714)"
                    });
                    break;
                }
            }
        })();

        utils.getRequestREST(site + "/_api/web/sitegroups?$select=title", "title").then(function (returnedData) {       //(that.site ...
            groups = returnedData;
            utils.getRequestREST(site + "/_api/web/siteusers", "loginname").then(function (moreReturnedData) {          //(that.site ...
                users = moreReturnedData;
                checkForMembers();
            });
        });

        //gets executed asynchroneously only
        function checkForMembers() {
            for (var iRoles = 0, iRolesSum = that.roles.length; iRoles < iRolesSum; iRoles++) {
                var offender = that.roles[iRoles].user || that.roles[iRoles].group;
                //ERROR HANDLING
                if (that.debugMode) {
                    if (offender.match(crudeSP.shared.wordSpaceWord)) {
                        throw new spqException("AssignRoles: invalid token", "illegal whitespace found in '" + offender + "' use comma to separate user-permissions (n. 715)");
                    }
                }
                // /ERROR HANDLING
                if (typeof that.roles[iRoles].user !== "undefined") {
                    var userArray = that.roles[iRoles].user.split(",");
                    for (var iUA = 0, iUASum = userArray.length; iUA < iUASum; iUA++) {
                        if (users.indexOf(userArray[iUA].replace(rolesRgx, "").trim()) === -1) { // RGX: /\w+[(\\)|(\/)]+/
                            //throw this message regardless of debug = true to prevent writing data with inherited permissions if a user/group is wrong
                            deferred.reject({
                                type: "[crudeSP] AssignRoles: invalid reference",
                                message: "User '" + userArray[iUA] + "' not found. " + opName + " aborted (n. 722)"
                            });
                            break;
                        }
                    }
                } else if (that.roles[iRoles].group !== "undefined") {
                    var groupArray = that.roles[iRoles].group.split(",");
                    for (var iGA = 0, iGASum = groupArray.length; iGA < iGASum; iGA++) {
                        if (groups.indexOf(groupArray[iGA].replace(rolesRgx, "").trim()) === -1) { // RGX: /\w+[(\\)|(\/)]+/
                            //throw this message regardless of debug = true to prevent writing data with inherited permissions if a user/group is wrong
                            deferred.reject({
                                type: "[crudeSP] AssignRoles: invalid reference",
                                message: "User '" + groupArray[iGA] + "' not found. " + opName + " aborted (n. 723)"
                            });
                            //throw new spqException(namedSource + " invalid reference", "User '" + groupArray[iGA] + "' not found. " + opName + " aborted (n. " + source + "422)");
                        }
                    }
                }
            }
            deferred.resolve();
        }
        return deferred.promise();
    };

    //////////////////////////////////////////// ##### /COMMONS ##### /////////////////////////////////////////////////

    ///////////////////////////////// ##### U T I L I T I E S ##### ///////////////////////////////////////

    ////////////////////// get siteusers and groups ////////////////////////
    utils.getRequestREST = function (endpoint, resource) {
        var deferred = new $.Deferred();
        var outputContainer = [];
        var request = new XMLHttpRequest();
        request.open("GET", endpoint, true);
        request.setRequestHeader("Accept", "application/json; odata=verbose");

        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                var data = JSON.parse(request.responseText);
                if (resource !== "folders") {    // I. enumerable data
                    for (var iRes = 0, iSum = data.d.results.length; iRes < iSum; iRes++) {
                        if (resource === "title") {
                            outputContainer.push(data.d.results[iRes].Title);
                        } else if (resource === "loginname") {
                            outputContainer.push(data.d.results[iRes].LoginName.replace(/[\w\:\.\|\#]+\\/, "")); //auth-tag + domain + \ l?schen
                        }
                    }
                    deferred.resolve(outputContainer);
                } else {    // II. one property (folder exists => data.d.ItemCount)
                    deferred.resolve(data.d.Folders.results);
                }
            }
        };
        request.send(null);
        return deferred.promise();
    };

    ////////////////////// /get siteusers and groups ////////////////////////

    ///////////////////// SP-Location /////////////////////
    utils.getContextWeb = function () {
        var deferred = new $.Deferred();
        if (typeof that.site === "undefined") {
            site = window.location.href.substring(0, window.location.href.indexOf("/SitePages/Home.aspx")); ////site = window.location.protocol + "//" + window.location.host;
            context = SP.ClientContext.get_current(site);
        } else {
            context = new SP.ClientContext(that.site);
            site = that.site;
        }
        web = context.get_web();

        if (that.debugMode) {
            utils.getRequestREST(site + "/_api/web/lists/getByTitle('" + primaryList + "')/fields", "title").then(gotListColumnNames);
        } else {
            gotListColumnNames();
        }

        function gotListColumnNames(data) {
            deferred.resolve(data);
        }

        return deferred.promise();

    };
    ///////////////////// /SP-Location /////////////////////

    ///////////////////// ItemLevel-Perms /////////////////////

    utils.deleteRolesPerRowWithPromise = function (listItemsArray, callBackFail) {
        var deferred = new $.Deferred();
        var copiedListItems = [];
        for (var iLIA = 0, iLIASum = listItemsArray.length; iLIA < iLIASum; iLIA++) {
            copiedListItems.push(listItemsArray[iLIA]); //arrays and other objects are addressed by reference, iterate and push into new array to prevent overwriting of original variables
        }

        (function doThat() {
            if (copiedListItems.length !== 0) {
                deleteRoles(copiedListItems[0]).then(function () {
                    copiedListItems.splice(0, 1);
                    doThat();
                }).fail(function (err) {
                    callBackFail(err);
                });
            } else {
                deferred.resolve();
            }
        })();

        return deferred.promise();

        function deleteRoles(listItem) {
            var innerDeferred = new $.Deferred();

            var perms = listItem.get_roleAssignments();
            listItem.breakRoleInheritance(true);
            context.load(perms);
            context.executeQueryAsync(function () {
                var assignments = [];
                var enumeratePerms = perms.getEnumerator();
                while (enumeratePerms.moveNext()) {
                    assignments.push(enumeratePerms.get_current());
                }

                for (var iAss = 0, iAssSum = assignments.length; iAss < iAssSum; iAss++) {
                    assignments[iAss].deleteObject();
                }

                context.executeQueryAsync(function () {
                    innerDeferred.resolve();
                }, function (sender, args) {
                    innerDeferred.reject(args.get_message());
                });

            }, function (sender, args) {
                innerDeferred.reject(args.get_message());
            });
            return innerDeferred.promise();
        }
    }

    utils.assignRoles = function (listItemsToPermit, callBackFail, callBackFn) {
        var userWeb = context.get_web();
        var roleTypesSP = [SP.RoleType.reader, SP.RoleType.editor, SP.RoleType.contributor, SP.RoleType.administrator, SP.RoleType.guest, SP.RoleType.webDesigner];
        var roles = [];

        if (that.roles.constructor !== Array) {
            roles.push(that.roles);
        } else {
            roles = that.roles;
        }

        for (var iR = 0, iRSum = roles.length; iR < iRSum; iR++) {
            var currUser;
            var currGroup;
            var user_group;

            if (typeof roles[iR].user !== "undefined") {
                currUser = roles[iR].user;
            } else if (typeof roles[iR].group !== "undefined") {
                currGroup = roles[iR].group;
            }

            if ((typeof currUser !== "undefined" && currUser !== "")) {
                //ACHTUNG: \t \n \r \f \b in strings are not escaped (problematic when entering domain and user name beginning with char)
                var escapeCharsRgx = [/\n/, /\r/, /\t/, /\f/];
                var escapeCharsStr = ["n", "r", "t", "f"];
                var userToEnsure;
                for (var iEC = 0; iEC < 6; iEC++) {
                    if (currUser.match(escapeCharsRgx[iEC])) {
                        userToEnsure = currUser.replace(escapeCharsRgx[iEC], "\\" + escapeCharsStr[iEC]);
                        break;
                    }
                }
                userToEnsure = userToEnsure.replace(/[\/]+/, "\\");
                user_group = userWeb.ensureUser(userToEnsure);
                currUser = "";
            } else if (typeof currGroup !== "undefined" && currGroup !== "") {
                user_group = userWeb.get_siteGroups().getByName(currGroup.trim());
                currGroup = "";
            }

            var roleDefBinding = SP.RoleDefinitionBindingCollection.newObject(context);

            for (var iRT = 0; iRT < 3; iRT++) {
                if (roles[iR].type === that.roleTypesInterface[iRT]) {
                    roleDefBinding.add(userWeb.get_roleDefinitions().getByType(roleTypesSP[iRT]));

                    for (var iLI = 0, iLISum = listItemsToPermit.length; iLI < iLISum; iLI++) {
                        var listItem = listItemsToPermit[iLI];
                        listItem.breakRoleInheritance(false);
                        listItem.get_roleAssignments().add(user_group, roleDefBinding);
                    }
                    break;
                }
            }
        } //roles

        context.executeQueryAsync(function () {
            if (typeof callBackFn !== "undefined") {        //2.7
                callBackFn();
            }
        }, permFail);

        function permFail(sender, args) {
            //callBackFail(args.$2J_2);
            callBackFail(args.get_message());
        }
    };
    ///////////////////// /ItemLevel-Perms /////////////////////

    //////////////////// FOLDERS ////////////////////////
    utils.folderExists = function (currFolder, alreadyProcessed, explicitList) {
        var deferred = new $.Deferred();
        //13.10.16 fullPath
        var folderTree;
        if (alreadyProcessed.constructor === Array) {
            folderTree = alreadyProcessed.join("/") || "";
            if (folderTree !== "") {
                folderTree = "/" + folderTree;
            }
        } else folderTree = "/" + alreadyProcessed;
        // /13.10.16 fullPath

        var list = that.list;
        if (explicitList) {
            list = explicitList;
        }

        utils.getRequestREST(site + "/_api/web/GetFolderByServerRelativeUrl('" + list + folderTree + "')?$expand=Folders", "folders").then(function (folders) {
            var folderFound = false;
            for (var iF in folders) {
                if (folders[iF].Name.trim() === currFolder.trim()) {
                    folderFound = true;
                    break;
                }
            }
            deferred.resolve(folderFound);
        });
        return deferred.promise();
    };

    utils.handleFolders = function (action, callBack, failedCallBack) {
        //regardless of debugMode
        if (typeof that.folders === "undefined") {
            failedCallBack("[crudeSP] Folders: missing parameters; folders not defined (n. 601)");
            return;
        }
        // /ERROR HANDLING

        utils.getContextWeb().then(function () {
            var list;
            try {
                list = new commons.GetListAndItems().list;
            } catch (ex) {
                failedCallBack(ex.type + "; " + ex.message);
                return;
            }

            //that.folders   - mandatory
            var folders = ensureArray(that.folders);

            //that.foldersNew    - optionally within handleFolders
            if (that.foldersNew) {
                var foldersRename = ensureArray(that.foldersNew);
                //ERROR HANDLING
                if (that.debugMode) {
                    if (foldersRename.length !== folders.length) {
                        failedCallBack("[crudeSP] Folders: invalid token; missmatch between length of that.folders and that.foldersNew (n. 612)");
                        return;
                    }
                } // /ERROR HANDLING
            }

            function ensureArray(source) {
                var target = [];
                if (source.constructor !== Array) {
                    target.push(source);
                } else if (source.constructor === Array) {
                    target = source;
                } else if (source.constructor !== Array) {
                    //ERROR HANDLING
                    if (that.debugMode) {
                        if (typeof source !== "string") {
                            failedCallBack("[crudeSP] Folders: invalid token; property folder must be of type string or array of strings (n. 611)");
                            return false;
                        }
                    } // /ERROR HANDLING
                }
                return target;
            }

            (function doIt() {
                function replaceSlashes(arrayMember) { //remove leading and trailing slashes
                    if (arrayMember.charAt(0) === "/") {
                        arrayMember = arrayMember.substr(1);
                    }
                    if (arrayMember.slice(-1) === "/") {
                        arrayMember = arrayMember.substr(0, arrayMember.length - 1);
                    }
                }

                replaceSlashes(folders[0]);
                if (foldersRename) { //[0]
                    replaceSlashes(foldersRename[0]);
                }

                var folderStruct = folders[0].split("/");
                var newFolderStruct;

                if (foldersRename) { //[0]
                    newFolderStruct = foldersRename[0].split("/");
                } else if (!foldersRename && that.roles) {
                    newFolderStruct = folders[0].split("/");
                }

                handleFolderPart({
                    parentFolder: list.get_rootFolder(),
                    alreadyProcessed: [],
                    folderStruct: folderStruct,
                    newFolderStruct: newFolderStruct, //only needed with action === "rename"
                    deleteArray: [],
                    folderItemsToPermit: [],
                    fail: failedCallBack,
                    deferred: new $.Deferred(),
                    recursiveCall: false
                }).then(function () {
                    folders.splice(0, 1);
                    if (foldersRename) {
                        if (foldersRename.length > 0) {
                            foldersRename.splice(0, 1);
                        }
                    }
                    if (folders.length > 0) {
                        doIt();
                    } else {
                        if (callBack) {
                            callBack();
                        }
                    }
                });
            })();
        });

        function handleFolderPart(def) {
            var currentFolderName = def.folderStruct[0]; //creating folders apparently doesn't need character encoding 
            if (action === "create") {
                createFolderItem();
            } else if (action === "delete") {
                handleDelete(currentFolderName);
            } else if (action === "rename") {
                handleUpdate(currentFolderName);
            }

            function handleDelete(folderName) {
                //collect items to delete during recursion
                //subfolders get deleted automatically when parent is removed
                if (def.folderStruct.length === 1) {
                    def.deleteArray.push({
                        item: folderName,
                        relPath: def.alreadyProcessed.join("/")
                    });
                }

                doRecursionRenameDelete("delete");

                //now delete 'em in a patch (doRecursionRenameDelete splices the arrays)
                if (def.folderStruct.length === 0) {
                    doIt();
                }

                function doIt() {
                    var folder = def.deleteArray[def.deleteArray.length - 1];
                    var deleteFolder = new csp.Operation({
                        site: that.site,
                        list: that.list,
                        where: "FileLeafRef = " + folder.item,
                    });

                    if (folder.relPath) {
                        deleteFolder.folderUrl = that.list + "/" + folder.relPath;
                    }

                    deleteFolder.deleteItems(function () {
                        def.deleteArray.splice(def.deleteArray[def.deleteArray.length - 1], 1);
                        if (def.deleteArray.length > 0) {
                            doIt();
                        }
                    }, function () {
                        if (!that.debugMode) {
                            console.log("[crudeSP] Folders: WARNING folder '" + currentFolderName + "' not found (n. W05)");
                        } else {
                            failedCallBack("[crudeSP] Folders: invalid reference; folder '" + currentFolderName + "' not found, operation aborted (n. 603)");
                            return;
                        }
                    });
                }
            }

            function handleUpdate(folderName) {
                //when a folders name hasn't changed, just go further down the path:
                if (def.newFolderStruct) {
                    if (folderName === def.newFolderStruct[0]) {
                        doRecursionRenameDelete("rename");
                        return;
                    }
                }

                if (that.foldersNew) {
                    var updateFolder = new csp.Operation({
                        site: that.site,
                        list: that.list,
                        where: "FileLeafRef = " + folderName,
                        set: {
                            FileLeafRef: def.newFolderStruct[0]
                        }
                    });

                    if (def.alreadyProcessed.length > 0) {
                        updateFolder.folderUrl = that.list + "/" + def.alreadyProcessed.join("/");
                    }

                    updateFolder.updateItems(function () {
                        doRecursionRenameDelete("rename");
                    }, function () {
                        if (!that.debugMode) {
                            console.log("[crudeSP] Folders: WARNING folder '" + currentFolderName + "' not found (n. W05)");
                            doRecursionRenameDelete("rename");
                        } else {
                            failedCallBack("[crudeSP] Folders: invalid reference; folder '" + currentFolderName + "' not found, operation aborted (n. 603)");
                            return;
                        }
                    });
                } else if (that.roles) {
                    doRecursionRenameDelete("rename");
                }
            }

            function doRecursionRenameDelete(operation) {
                var currFolder;
                var folderTree = that.list;

                if (operation === "rename") {
                    def.alreadyProcessed.push(def.newFolderStruct[0]);
                    folderTree += "/" + def.alreadyProcessed.join("/");// + "/" + def.newFolderStruct[0];
                } else if (operation === "delete") {
                    folderTree += "/" + def.alreadyProcessed.join("/");// + "/" + folderName;
                    def.alreadyProcessed.push(def.folderStruct[0]);
                }

                currFolder = web.getFolderByServerRelativeUrl(folderTree);

                if (def.folderStruct.length > 1) {
                    def.folderStruct.splice(0, 1);
                    var newInstance = {
                        parentFolder: currFolder,
                        folderStruct: def.folderStruct,
                        alreadyProcessed: def.alreadyProcessed,
                        folderItemsToPermit: def.folderItemsToPermit,
                        fail: def.fail,
                        recursiveCall: true,
                        deferred: def.deferred
                    };

                    if (operation === "rename") {
                        def.newFolderStruct.splice(0, 1);
                        newInstance.newFolderStruct = def.newFolderStruct;
                    } else if (operation === "delete") {
                        newInstance.deleteArray = def.deleteArray;
                    }

                    //RECURSION UNTIL ARRAY IS EMPTY
                    handleFolderPart(newInstance);
                } else {
                    if (operation === "rename") {
                        def.folderItemsToPermit.push(currFolder.get_listItemAllFields());
                    }

                    def.folderStruct = [];
                    def.newFolderStruct = [];

                    if (operation === "rename" && that.roles) {
                        var checkArrObj = that.roles[0] || that.roles;
                        if (checkArrObj.deleteExisting) {
                            //Sharepoint deletes all roles in descendant folders, therefore cascade...
                            cascadingSeek({
                                entryPoint: that.list + "/" + def.alreadyProcessed.join("/"),
                                parentFolder: def.parentFolder,
                                items: def.folderItemsToPermit
                            }).then(function () {
                                utils.deleteRolesPerRowWithPromise(def.folderItemsToPermit, def.fail).then(function () {
                                    utils.assignRoles(def.folderItemsToPermit, def.fail);
                                    def.deferred.resolve();
                                });
                            });
                        } else {
                            utils.assignRoles(def.folderItemsToPermit, def.fail);
                            def.deferred.resolve();
                        }
                    } else {
                        def.deferred.resolve();
                    }
                }
            }

            function createFolderItem() {
                var currFolder = def.parentFolder.get_folders().add(currentFolderName);
                context.load(currFolder);
                context.executeQueryAsync(function () {
                    def.folderItemsToPermit.push(currFolder.get_listItemAllFields());
                    def.alreadyProcessed.push(currentFolderName);
                    if (def.folderStruct.length > 1) {
                        def.folderStruct.splice(0, 1);

                        //RECURSION UNTIL ARRAY IS EMPTY
                        handleFolderPart({
                            parentFolder: currFolder,
                            alreadyProcessed: def.alreadyProcessed,
                            folderStruct: def.folderStruct,
                            folderItemsToPermit: def.folderItemsToPermit,
                            fail: def.fail,
                            recursiveCall: true,
                            deferred: def.deferred
                        });
                    } else {
                        def.folderStruct = [];
                        if (that.roles) {
                            utils.assignRoles(def.folderItemsToPermit, def.fail);
                        }
                        def.deferred.resolve();
                    }
                }, def.fail);
            }

            if (!def.recursiveCall) {
                return def.deferred.promise();
            }
        }

        function cascadingSeek(def) {
            var folderTree = def.entryPoint;
            var deferred = new $.Deferred();
            var accumulatedArr = def.items;

            (function doRecursive(folderStruct) {
                utils.getRequestREST(site + "/_api/web/GetFolderByServerRelativeUrl('" + folderStruct + "')?$expand=Folders,ListItemAllFields", "folders").then(function (folders) {
                    if (folders.length > 0) {
                        for (var i in folders) {
                            folderStruct += "/" + folders[i].Name;
                            var currFolder = web.getFolderByServerRelativeUrl(folderStruct);
                            accumulatedArr.push(currFolder.get_listItemAllFields());
                            doRecursive(folderStruct);  //, currFolder
                        }
                    } else {
                        deferred.resolve(accumulatedArr);
                    }
                });
            })(folderTree, def.parentFolder);

            return deferred.promise();
        }
    }
    //////////////////// /FOLDERS ////////////////////////

    ///////////////////////////////// ##### /U T I L I T I E S ##### ///////////////////////////////////////

    ///////////////////////////////// ##### CRUD Methods ##### ///////////////////////////////////////

    ///////////////////// ReadItems /////////////////////
    this.readItems = function (callBack, callBackFail) {
        var outputArray = [];
        var listNEntities;
        var list;
        var listItems;

        //regardless of debugMode
        if (typeof callBack === "undefined" && typeof callBackFail !== "undefined") {
            callBackFail("[crudeSP] ReadItems: missing parameters; no callback function given (n. 101)");
        }
        // /ERROR HANDLING

        utils.getContextWeb().then(function (data) {
            listColumns = data;
            try {
                listNEntities = new commons.GetListAndItems();
            } catch (e) {
                callBackFail(e.type + "; " + e.message);
                return;
            }

            list = listNEntities.list;
            listItems = listNEntities.listItems;
            context.load(list);
            context.load(listItems);
            context.executeQueryAsync(readingList, listFail);
        });

        //on success
        function readingList() {
            if (that.debugMode) { //warining in debug-mode
                var warningShown = false;
                var warnAlias = "";
            }

            commons.iteratorFn(listItems, processEntityToObj);

            function processEntityToObj(tableRow) { //gets executed in while-loop of commons.iteratorFn
                if (tableRow) {
                    var containerObj = {};
                    var properties = [];
                    var property;

                    if (viewFlds.length === 0) { //caml-builder got bypassed with getItemById 
                        crudeSP.shared.camlSelectFlds = that.caml_select.split(",");
                        for (var iCaml = 0, iCamlSum = crudeSP.shared.camlSelectFlds.length; iCaml < iCamlSum; iCaml++) {
                            crudeSP.shared.handleInlineExp(iCaml);
                        }
                        viewFlds = crudeSP.shared.camlSelectFlds;
                    }

                    for (var iVFlds = 0, iVFldsSum = viewFlds.length; iVFlds < iVFldsSum; iVFlds++) {
                        viewFlds[iVFlds] = crudeSP.shared.encodeNames(viewFlds[iVFlds]); //=> SPENCODE
                        property = selectAs[iVFlds].replace(new RegExp(crudeSP.shared.allowedTabCol.source + "\\_SEP\\_"), "");
                        //ERROR HANDLING - WARNING: 
                        if (that.debugMode && !warningShown) {
                            if (properties.indexOf(property) !== -1) {
                                warnAlias = property;
                            } else {
                                properties.push(property);
                            }
                            if (warnAlias.length !== 0) {
                                warningShown = true;
                                console.log("[crudeSP] ReadItems - WARNING: Fieldname '" + warnAlias + "' occurs in more than one list, please specify an alias to distinguish the fields (n. W02)");
                            }
                        }
                        // /ERROR HANDLING - WARNING
                        if (viewFlds[iVFlds].match(new RegExp(crudeSP.shared.allowedTabCol.source + "\\."))) { //=> internal columname with alias  + _SEP_ + columnname     RGX: /^(.+?)\./
                            viewFlds[iVFlds] = viewFlds[iVFlds].replace(/\./g, "_SEP_");
                        }

                        if (tableRow.get_item(viewFlds[iVFlds]) !== null) { //sap-eternityDummy 9999-12-31 is displayed as null
                            if (tableRow.get_item(viewFlds[iVFlds]).constructor.getName() === "SP.FieldLookupValue") {
                                containerObj[property] = tableRow.get_item(viewFlds[iVFlds]).get_lookupValue();
                            } else {
                                containerObj[property] = tableRow.get_item(viewFlds[iVFlds]);
                            }
                        } else {
                            containerObj[property] = "";
                        }
                    }

                    if (!$.isEmptyObject(crudeSP.shared.inlineProps)) {
                        for (var prop in crudeSP.shared.inlineProps) {
                            if (crudeSP.shared.inlineProps.hasOwnProperty(prop)) {
                                containerObj[prop] = crudeSP.shared.inlineProps[prop];
                            }
                        }
                    }
                    outputArray.push(containerObj);
                } else {
                    listFail();
                }
            }

            //trigger callback function
            callBack(outputArray);
        }

        //on error
        function listFail(sender, args) {
            if (typeof callBackFail !== "undefined") {
                if (args) {
                    callBackFail(args.get_message());
                } else {
                    callBackFail();
                }
            }
        }
    };

    ///////////////////// II UpdateItems /////////////////////
    this.updateItems = function (callBack, callBackFail) {
        var listNEntities;
        var list;
        var listItems;
        var listItemsToPermit = [];
        var errorFound = false;

        //regardless of debugMode
        if (typeof that.set === "undefined" && typeof that.roles === "undefined") {
            executeCallbackFail("[crudeSP] UpdateItems: missing parameters", "no set or roles operation defined in update (n. 201)");
        }
        // /ERROR HANDLING

        utils.getContextWeb().then(function (data) {
            if (typeof that.roles !== "undefined") {
                try {
                    //do it even when debug is falsy
                    findRolesErrors();
                } catch (e) {
                    callBackFail(e.type + "; " + e.message);
                    return;
                }
                commons.checkIfUsersGroupsExist(2).then(doTheUpdate).fail(function (err) {
                    callBackFail(err.type + "; " + err.message);
                });
            } else {
                doTheUpdate();
            }

            function doTheUpdate() {
                listColumns = data;
                try {
                    listNEntities = new commons.GetListAndItems();
                } catch (exc) {
                    callBackFail(exc.type + "; " + exc.message);
                    return;
                }

                list = listNEntities.list;
                listItems = listNEntities.listItems;
                context.load(listItems);
                context.executeQueryAsync(updatingList, updateFailed);
                //});
            }
        });
        //on success
        function updatingList() {
            var errors = false;
            commons.iteratorFn(listItems, processEntityUpdate);
            if (!errors) {
                context.executeQueryAsync(updateSuccess, updateFailed);
            }

            function processEntityUpdate(tableRow) {
                if (tableRow) {
                    listItemsToPermit.push(tableRow);

                    if (typeof that.set !== "undefined") {
                        if (typeof that.set === "object") {
                            //regardless of debugMode:
                            if (that.set.constructor === Array) {
                                executeCallbackFail("[crudeSP] UpdateItems: invalid token; set cannot be an array (n. 215)");
                                return;
                            }

                            for (var key in that.set) {
                                if (that.set.hasOwnProperty(key)) {
                                    var sliceLeftObj = crudeSP.shared.encodeNames(key); //=> SPENCODE
                                    var sliceRightObj = that.set[key];
                                    //FEHLERBEHANLDUNG
                                    if (that.debugMode) {
                                        try {
                                            commons.findFieldErrors(key, 2, false);
                                        } catch (exce) {
                                            callBackFail(exce.type + "; " + exce.message);
                                            errors = true;
                                            return;
                                        }
                                    }
                                    // /ERROR HANDLING
                                    var isDate = crudeSP.shared.handleDates(sliceRightObj, "write");
                                    if (isDate) {
                                        sliceRightObj = isDate; //writing to sharepoint, dates need to be presented as utc

                                    }
                                    tableRow.set_item(sliceLeftObj, sliceRightObj); //=> SPENCODE
                                }
                            }
                            tableRow.update();
                        } else {
                            executeCallbackFail("[crudeSP] UpdateItems: invalid token; values must be of type object (n. 216)");
                            errors = true;
                            return;
                        }
                    }
                } else {
                    executeCallbackFail();
                    errors = true;
                    return;
                }
            }
        }

        function updateSuccess() {
            if (affectedRows > 0) {

                if (typeof that.roles !== "undefined") {
                    var roleObjectOrArray = that.roles[0] || that.roles;

                    if (roleObjectOrArray.deleteExisting) {
                        utils.deleteRolesPerRowWithPromise(listItemsToPermit, callBackFail).then(function () {
                            if (!errorFound) {
                                utils.assignRoles(listItemsToPermit, executeCallbackFail);
                                executeCallback();
                            }
                        });
                    } else {
                        utils.assignRoles(listItemsToPermit, executeCallbackFail);
                        executeCallback();
                    }
                }
                else {
                    executeCallback();
                }
            }
        }

        function executeCallback() {
            if (typeof callBack !== "undefined") {
                callBack(); //callback-Fn is optional
            }
        }

        function updateFailed(sender, args) {   //sharepoint csom-mode (sender, args)
            if (typeof callBackFail !== "undefined") {
                callBackFail(args.get_message());
            }
        }

        function executeCallbackFail(message) {     //csp-internal errors (message)
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }
    };

    ///////////////////// DeleteItem /////////////////////
    this.deleteItems = function (callBack, callBackFail) {
        var listNEntities;
        var list;
        var listItems;
        var listItemIdToDelete = [];

        utils.getContextWeb().then(function (data) {
            listColumns = data;
            try {
                listNEntities = new commons.GetListAndItems();
            } catch (e) {
                callBackFail(e.type + "; " + e.message);
                return;
            }
            list = listNEntities.list;
            listItems = listNEntities.listItems;
            listItemIdToDelete = [];
            context.load(listItems);
            context.executeQueryAsync(deleting, deleteFailed);
        });

        //on success
        function deleting() {
            commons.iteratorFn(listItems, processEntityDelete);

            function processEntityDelete(tableRow) {
                if (affectedRows > 0) {
                    listItemIdToDelete.push(tableRow);
                }
            }

            if (affectedRows > 0) {
                for (var iTd = 0, toDeleteSum = listItemIdToDelete.length; iTd < toDeleteSum; iTd++) {
                    listItemIdToDelete[iTd].deleteObject();
                }
                context.executeQueryAsync(deleteSuccess, deleteFailed);
            } else {
                deleteSuccess();    //no rows affected no fatal error
            }
        }

        function deleteFailed(sender, args) {
            if (typeof callBackFail !== "undefined") {
                callBackFail(args.get_message());
            }
        }

        function deleteSuccess() {
            if (typeof callBack !== "undefined") {
                callBack();       //callback-Fn is optional
            }
        }
    };

    ///////////////////// Create /////////////////////
    this.createItems = function (callBack, callBackFail) {
        var listItemsToPermit = [];
        var list;
        var listItem;

        //regardless of debugMode
        if (typeof that.values === "undefined") {
            executeCallbackFail("[crudeSP] CreateItems: missing parameters; no values entered (n. 401)");
        }
        // /ERROR HANDLING

        utils.getContextWeb().then(function (data) {
            listColumns = data;
            if (typeof that.roles !== "undefined") {
                try {
                    findRolesErrors();
                } catch (e) {
                    executeCallbackFail(e.type + "; " + e.message);
                    return;
                }
                commons.checkIfUsersGroupsExist(4).then(function () {
                    doCreate();
                }).fail(function (err) {
                    executeCallbackFail(err.type + "; " + err.message);
                });
            } else {
                doCreate();
            }
        });

        function doCreate() {
            list = web.get_lists().getByTitle(primaryList);

            if (typeof that.values === "object") {
                var container = [];
                if (that.values.constructor !== Array) {        //2.7
                    container.push(that.values);
                } else {
                    container = that.values;
                }

                for (var obj in container) {
                    var itemOb = new SP.ListItemCreationInformation();
                    listItem = list.addItem(itemOb);
                    listItemsToPermit.push(listItem);

                    for (var key in container[obj]) {
                        if (container[obj].hasOwnProperty(key)) {
                            //ERROR HANDLING
                            if (that.debugMode) {
                                try {
                                    commons.findFieldErrors(key, 4, false);
                                } catch (exce) {
                                    callBackFail(exce.type + "; " + exce.message);
                                    return;
                                }
                            }
                            // /ERROR HANDLING
                            var sliceLeftObj = crudeSP.shared.encodeNames(key); //=>SPENCODE
                            var sliceRightObj = container[obj][key];
                            var isDate = crudeSP.shared.handleDates(sliceRightObj, "write");
                            if (isDate) {
                                sliceRightObj = isDate;
                            }

                            listItem.set_item(sliceLeftObj, sliceRightObj); //=>SPENCODE
                        }
                    }
                    listItem.update();

                    context.load(listItem);
                }
            } else {
                executeCallbackFail("[crudeSP] CreateItems: invalid token; values must be of type object or array (n. 416)");
                return;
            }
            context.executeQueryAsync(createSuccess, createFailed);
        }

        function createFailed(sender, args) {       //sharepoint csom-mode (sender, args)
            if (typeof callBackFail !== "undefined") {
                callBackFail(args.get_message());
            }
        }

        function executeCallbackFail(message) {     //csp-internal errors (message)
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }

        function createSuccess() {
            if (typeof callBack !== "undefined") {
                callBack(listItem.get_objectData().$1h_0.$m_dict); //callBack(listItem.get_id()); //callback-Fn is optional!
            }
            if (typeof that.roles !== "undefined") {
                utils.assignRoles(listItemsToPermit, callBackFail);
                callBack(listItem.get_objectData().$1h_0.$m_dict);
            }
        }
    };
    ///////////////////////////////// ##### /CRUD Methods ##### ///////////////////////////////////////

    ///////////////////////////////// ##### Upload To Library ##### /////////////////////////////////
    //6.7.16
    this.deleteFolders = function (callBack, callBackFail) {
        utils.handleFolders("delete", callBack, failedCallBack);

        function failedCallBack(message) {
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }
    }
    //6.7.16

    //5.7.16
    this.updateFolders = function (callBack, callBackFail) {
        //regardless of debugMode
        if (typeof that.foldersNew === "undefined" && typeof that.roles === "undefined") {
            failedCallBack("[crudeSP] Folders: missing parameters; folderNew not defined (n. 602)");
            return;
        }
        // /ERROR HANDLING

        utils.handleFolders("rename", callBack, failedCallBack);

        function failedCallBack(message) {
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }
    };
    // /5.7.16

    //27.11.15
    this.createFolders = function (callBack, callBackFail) {
        if (that.roles !== "undefined") {
            //If the role (read, write, contribute etc.) or user not exists, don't even consider uploading...
            //do this even when debug is falsy
            try {
                findRolesErrors();
            } catch (e) {
                failedCallBack(e.type + "; " + e.message);
                return;
            }
            utils.getContextWeb().then(function () {
                commons.checkIfUsersGroupsExist(6).then(function () {
                    utils.handleFolders("create", callBack, failedCallBack);
                }).fail(function (err) {
                    failedCallBack(err.type + "; " + err.message);
                });
            });
        } else {
            utils.handleFolders("create", callBack, failedCallBack);
        }

        function failedCallBack(message) {
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }
    };
    // /27.11.15

    this.uploadToLibrary = function (callBack, callBackFail) {
        var listItemId;
        var listToProcess;

        //regardless of debugMode
        if (typeof that.file === "undefined") {
            failedCallBack("[crudeSP] Upload to library: missing parameters; no file provided (n. 501)");
            return;
        }
        // /ERROR HANDLING

        //if (typeof that.site === "undefined") {
        //    that.site = window.location.href.substring(0, window.location.href.indexOf("SitePages/Home.aspx"));
        //}

        if (that.roles !== "undefined") {
            //If the role (read, write, contribute etc.) or user not exists, don't even consider uploading...
            //do this even when debug is falsy
            try {
                findRolesErrors();
            } catch (e) {
                failedCallBack(e.type + "; " + e.message);
                return;
            }

            utils.getContextWeb().then(function () {
                commons.checkIfUsersGroupsExist(5).then(doIt).fail(function (err) {
                    failedCallBack(err.type + "; " + err.message);
                });
            });
        } else {
            doIt();
        }

        function doIt() {
            getFileBuffer(that.file).then(function (content) {
                if (typeof that.filename === "undefined") {
                    that.filename = that.file.name;
                }
                //27.11.15
                if (typeof that.folders !== "undefined") {
                    if (that.folders.charAt(0) !== "/") {
                        that.folders = "/" + that.folders;
                    }
                    listToProcess = that.list + that.folders;
                } else {
                    listToProcess = that.list;
                }
                // /27.11.15

                var endpoint = site + "/_api/web/GetFolderByServerRelativeUrl('" + listToProcess + "')/Files" +
                    "/Add(url='" + that.filename + "', overwrite=true)?$expand=ListItemAllFields";
                var request = new XMLHttpRequest();
                request.open("POST", endpoint, true);

                request.setRequestHeader("Accept", "application/json; odata=verbose");
                request.setRequestHeader("X-RequestDigest", $("#__REQUESTDIGEST").val());
                request.processData = false;
                var done = false;
                request.onreadystatechange = function (data) {
                    if (request.readyState === 4 && !done) {
                        done = true;
                        if (typeof that.set !== "undefined" || typeof that.roles !== "undefined") {
                            listItemId = JSON.parse(data.currentTarget.response).d.ListItemAllFields.Id;
                            var setFieldsWhereFileUploaded = new csp.Operation({
                                list: that.list.replace(/\/.*/, ""),        //09.05.2016 - when using folders 'list' is a path that obviously cannot be found as list in a sp-site
                                where: listItemId,
                                debug: that.debugMode || false
                            });
                            if (typeof that.set !== "undefined") {
                                setFieldsWhereFileUploaded.set = that.set;
                            }
                            if (typeof that.roles !== "undefined") {
                                setFieldsWhereFileUploaded.roles = that.roles;
                            }
                            if (typeof site !== "undefined") {
                                setFieldsWhereFileUploaded.site = site;
                            }

                            setFieldsWhereFileUploaded.updateItems(successfulCallBack, callBackFail);
                        }
                    }
                };

                request.onerror = function (message) {
                    callBackFail(message);
                };
                request.send(content);
            });
        }

        function failedCallBack(message) {
            if (typeof callBackFail !== "undefined") {
                callBackFail(message);
            }
        }

        function successfulCallBack() {
            if (typeof callBack !== "undefined") {
                callBack();
            }
        }

        function getFileBuffer(file) {
            //foreign code - Scott Hillier: http://www.shillier.com/archive/2013/03/26/uploading-files-in-sharepoint-2013-using-csom-and-rest.aspx 
            var deferredInner = $.Deferred();
            var reader = new FileReader();
            reader.onload = function (e) {
                deferredInner.resolve(e.target.result);
            }

            reader.onerror = function (e) {
                deferredInner.reject(e.target.error);
            }

            reader.readAsArrayBuffer(file);
            return deferredInner.promise();
        };
    }

    this.moveFiles = function (callBack, callBackFail) {
        doIt();
        var src;

        function doIt() {
            var sourcePath = that.source[0].replace(/^\w+\//, "");
            var sourceFolder = that.source[0].match(/(\/\w+$)|(^\w+$)/)[0].replace(/\//, "");
            var sourceList = that.source[0].match(/^\w+/)[0];
            var destPath = that.destination[0].replace(/^\w+\//, "");
            var destFolder = that.destination[0].match(/(\/\w+$)|(^\w+$)/)[0].replace(/\//, "");
            var destList = that.destination[0].match(/^\w+/)[0];

            sourcePath = sourcePath.replace(/[\/]?\w+$/, "");
            destPath = destPath.replace(/[\/]?\w+$/, "");

            var errorMessage = "[crudeSP] Folders: invalid reference; folder '" + sourceFolder + "' not found, operation aborted (n. 603)";

            utils.getContextWeb().then(function () {
                src = web.getFolderByServerRelativeUrl(that.source[0]);
                context.load(src, "Files");
                if (that.debugMode) {
                    utils.folderExists(sourceFolder, sourcePath, sourceList).then(function (found) {
                        if (!found) {
                            callBackFail(errorMessage);
                            return;
                        }
                        utils.folderExists(destFolder, destPath, destList).then(function (found2) {
                            if (!found2) {
                                callBackFail(errorMessage);
                                return;
                            }
                            context.executeQueryAsync(movingFiles, movingFailed);
                        });
                    });
                } else {
                    context.executeQueryAsync(movingFiles, movingFailed);
                }
            });
        };

        function movingFiles() {
            var files = src.get_files();
            var enumerator = files.getEnumerator();
            while (enumerator.moveNext()) {
                var currFile = enumerator.get_current();
                var destUrl = that.destination[0] + "/" + currFile.get_name();
                currFile.moveTo(destUrl, SP.MoveOperations.overwrite);
            }
            context.executeQueryAsync(movingSuccess, movingFailed);
        }

        function movingSuccess() {
            that.source.splice(0, 1);
            that.destination.splice(0, 1);
            if (that.source.length === 0) {
                if (typeof callBack !== "undefined") {
                    callBack();
                }
            } else {
                doIt();
            }
        }

        function movingFailed(sender, args) {       //sharepoint csom-mode (sender, args)
            if (typeof callBackFail !== "undefined") {
                callBackFail(args.get_message());
            }
        }
    };

    ///////////////////////////////// ##### /Upload To Library ##### /////////////////////////////////

    /////////////////////////////// /O P E R A T I O N //////////////////////////////
}
// /[crudeSP]


